import md5 from 'md5';
import { AxiosError } from 'axios';
import type { AxiosInstance, AxiosResponse } from 'axios';

import { getName, getVersion } from '@tauri-apps/api/app';
// eslint-disable-next-line import/no-cycle
import {
  useUsersStore, useSysStore, convertReqStatistics, useGameStore,
} from '@/store';
import { MessageState } from '@/contrib/message';
import { showLoginModal, showNameCheck } from '@/components/modals';
import { generateYunDunReqToken, getOsArch } from '@/contrib/common';
import { generateRndStr } from '@/contrib/utils';
import { showLoading } from '@/components/loading';
import { Exception, OdinException } from '@/exception';
import { objcontainField } from '@/contrib/types';
import { ErrCode } from '@/exception/error-code';
import router from '@/router';
import { getRealIp } from '@/contrib/network';
import createDebug from '@/contrib/debug';
import { OdinTokenKey, YundunToken } from './key';
import { odinApi } from '.';

const debug = createDebug('worker:请求拦截器');

// 全局加载 loading 状态
type LoadingType = ReturnType<typeof showLoading>;
// 请求上下文日志显示
const requestDebug = false;
const warnMessage = new MessageState();
// function disableApiCache(client: AxiosInstance) {
//   // client.interceptors.
//   client.interceptors.request.use((cfg) => {
//     cfg.headers['Cache-Control'] = 'no-store';
//     return cfg;
//   });
// }

function setGlobalLoading(client: AxiosInstance) {
  // 加载中 请求个数
  let loadingCount = 0;
  let globalLoading: LoadingType | null = null;
  let globalReqFinished = false;
  let showLoadingWhiteSwitch = true;
  // 300ms 后还未收到请求 则展示 loading 窗口
  const reqShowLoading = 300;

  // 拦截器
  client.interceptors.request.use((cfg) => {
    loadingCount++;
    // 只有第一次才真的去显示
    if (loadingCount === 1) {
      const data = localStorage.getItem('showLoadingWhitelist');
      const whiteList:string[] = data ? JSON.parse(data) : [];
      const str = `api/${cfg.url?.split('api/')[1]}`;
      // if(str.indexOf('api/check_order') !== -1){
      //   debug("充值中心数据未加载完毕前有没有请求查询接口",cfg);
      // }
      // 白名单查询
      whiteList.forEach((item) => {
        if (str.indexOf(item) !== -1) {
          showLoadingWhiteSwitch = false;
        }
      });
      globalReqFinished = false;
      const gameStore = useGameStore();
      setTimeout(() => {
        // 如果还有请求未完成 且 加载中DOM 未初始化
        // debug(`check: globalReqFinished: [${globalReqFinished}], count: [${loadingCount}]`);
        if (globalReqFinished === false && globalLoading === null && showLoadingWhiteSwitch && !gameStore.firstLoad) {
          globalLoading = showLoading();
        }
      }, reqShowLoading);
    }
    return cfg;
  });

  const hideLoading = () => {
    loadingCount--;
    // 归0时隐藏
    if (loadingCount === 0) {
      globalLoading?.close();
      globalLoading = null;
      globalReqFinished = true;
      showLoadingWhiteSwitch = true;
      // debug('finished, set globalReqFinished to true');
    }
  };
  client.interceptors.response.use((rsp) => {
    hideLoading();
    return rsp;
  }, (err) => {
    hideLoading();
    return Promise.reject(err);
  });
}

const requestBeginAtHeaderName = 'odin-request-begin-at';

/**
 *
 * 不使用 CDN 时的请求统计
 */
async function processRequestStatistics(rsp: AxiosResponse): Promise<boolean> {
  const now = +(new Date());
  const beginAt = rsp.config.headers[requestBeginAtHeaderName];
  if (!beginAt || typeof (beginAt) !== 'string') {
    console.warn(`field ${requestBeginAtHeaderName} invalid, ignored`);
    return false;
  }
  const serverStats = rsp.headers['x-server-statistics'];
  // reqBeginAt: unknown, cdnId: unknown, serverStats: unknown

  if (!serverStats) {
    // console.warn(`server statistics or cdnid empty [${rsp.config.url}], ignored`);
    return false;
  }
  if (typeof serverStats !== 'string') {
    console.warn('server statistics is invalid, ignored');
    return false;
  }
  // debug(rsp.config.headers)
  const start = parseInt(beginAt);
  if (Number.isNaN(start)) {
    return false;
  }
  const statLists = serverStats.trim().split(' ').map((item) => parseInt(item));
  if (statLists.length < 2) {
    console.warn(`x-server-statistics length: ${statLists.length}, ignored`);
    return false;
  }
  const rcvTime = statLists[0];
  const sndTime = statLists[1];

  const userStore = useUsersStore();
  let userId = 0;
  if (userStore.isAuthorized) {
    userId = userStore.user!.userId;
  }

  // 由于不再使用CDN，这里的唯一请求ID用一个随机数替代
  const reqId = Math.random().toString(36).substring(2);
  const sysStore = useSysStore();
  sysStore.putOne({
    client: [start, now],
    server: [rcvTime, sndTime],
    reqId,
    userId,
    url: rsp.config.url || '',
  });

  if (sysStore.reqStatCount >= 10) {
    const rs = sysStore.takeAll();
    const req = rs.map((i) => convertReqStatistics(i));
    await odinApi.default.uploadCdnLog({
      requestBody: req,
    });
    debug('upload cdn log finished...');
  }
  return true;
}

/**
 * 处理请求计时统计
 * 将服务器返回的响应头中的时间解析后，与客户端统计得到的时间
 * 一并存入 vuex/pinia 等到存够10个请求后，发送到服务器；
 * 三个参数都是 unknown 的原因是 从 axios header 里取的
 *
 * 返回 true 代表已正常处理
 * 返回 false 意味着未处理此请求
* */
// async function processRequestStatViaCdn(rsp: AxiosResponse): Promise<boolean> {
//   const now = +(new Date());
//   const beginAt = rsp.config.headers[requestBeginAtHeaderName];
//   if (!beginAt || typeof (beginAt) !== 'string') {
//     console.warn(`field ${requestBeginAtHeaderName} invalid, ignored`);
//     return false;
//   }
//   const cdnReqId = rsp.headers['x-request-id'];
//   const serverStats = rsp.headers['x-server-statistics'];
//   // reqBeginAt: unknown, cdnId: unknown, serverStats: unknown

//   if (!serverStats || !cdnReqId) {
//     // console.warn(`server statistics or cdnid empty [${rsp.config.url}], ignored`);
//     return false;
//   }
//   if (typeof serverStats !== 'string' || typeof cdnReqId !== 'string') {
//     console.warn('server statistics or cdnReqId is invalid, ignored');
//     return false;
//   }
//   // debug(rsp.config.headers)
//   const start = parseInt(beginAt);
//   if (Number.isNaN(start)) {
//     return false;
//   }
//   const statLists = serverStats.trim().split(' ').map((item) => parseInt(item));
//   if (statLists.length < 2) {
//     console.warn(`x-server-statistics length: ${statLists.length}, ignored`);
//     return false;
//   }
//   const rcvTime = statLists[0];
//   const sndTime = statLists[1];

//   const userStore = useUsersStore();
//   let userId = 0;
//   if (userStore.isAuthorized) {
//     userId = userStore.user!.userId;
//   }

//   const sysStore = useSysStore();
//   sysStore.putOne({
//     client: [start, now],
//     server: [rcvTime, sndTime],
//     reqId: cdnReqId,
//     userId,
//     url: rsp.config.url || '',
//   });

//   if (sysStore.reqStatCount >= 10) {
//     const rs = sysStore.takeAll();
//     const req = rs.map((i) => convertReqStatistics(i));
//     await odinApi.default.uploadCdnLog({
//       requestBody: req,
//     });
//     debug('upload cdn log finished...');
//   }
//   return true;
// }

/**
 * 请求计时
 * 服务器响应时，每个响应都应加入 x-server-statistics: 124123124, 1412312414
 * 两个数字分别是 服务器接受到请求的时间戳，处理为请求后的时间戳，按毫秒计时 空格分隔
 */
function setRequestTiming(client: AxiosInstance) {
  client.interceptors.request.use((cfg) => {
    cfg.headers[requestBeginAtHeaderName] = +(new Date());
    return cfg;
  });
  client.interceptors.response.use((rsp) => {
    processRequestStatistics(rsp);
    return rsp;
  }, (err) => {
    // 如果有正常收到响应的话，也应该处理
    if (err instanceof AxiosError && err.response) {
      const rsp = err.response;
      processRequestStatistics(rsp);
    }
    return Promise.reject(err);
  });
}

/**
 * 云盾鉴权接入
1. 客户端请求的时候带上特定的头部 X-YD-Req-Token, 值为加密后的结果
2. 节点在收到请求时对 X-YD-Req-Token 进行校验，校验成功则通过，正常响应，校验失败则拒绝，响应 464
加密方式

MD5= md5（Timestamp+NONCE+AUTH_TOKEN+URL）
示例：MD5=md5(1581577282Zod2Lhy2nLj81964c708b9f1231c96b3aa0db0d6772ehttp://www.yundun.com/token?auth=1) 最终生成MD5为：fc7f02dc369206905f9a41a12297a783
发起请求

Timestamp, NONCE, MD5, 用“|”拼接并赋值给 X-YD-Req-Token: Timestamp|NONCE|MD5 最终生成的 X-YD-Req-Token 如下：
X-YD-Req-Token: 1581577282|Zod2Lhy2nLj8|fc7f02dc369206905f9a41a12297a783
客户端再发起请求的时候，增加请求头 X-YD-Req-Token 即可
* */
function setYunDunInterceptor(client: AxiosInstance) {
  client.interceptors.request.use((config) => {
    const fullUrl = config.url;
    if (!fullUrl) {
      console.warn('url is empty?????');
      return config;
    }

    const timestamp = Math.floor(+(new Date()) / 1000);
    const nonce = generateRndStr(16);
    const ydToken = generateYunDunReqToken(YundunToken, timestamp, nonce, fullUrl);
    requestDebug && debug(`ydToken: [${ydToken}]`);
    config.headers['X-YD-Req-Token'] = ydToken;
    return config;
  });
}

let clientVersion = '';
let osArch = '';
export async function initClientVerInfo() {
  const appName = await getName();
  const appVer = await getVersion();
  clientVersion = `${appName} v${appVer}`;
  const [os, arch] = await getOsArch();
  osArch = `${arch} ${os}`;
}

/**
 * 给请求头加入客户端控制参数，经请求使用的api版本，客户端版本，客户端平台等发送出去
 */
function setOdinServerInterceptor(client: AxiosInstance) {
  // 客户端才需要这个
  if (window.__TAURI_IPC__) {
    client.interceptors.request.use((config) => {
      const apiVersion = 'v1';
      config.headers['odin-api-version'] = apiVersion;

      if (clientVersion) {
        config.headers['client-version'] = clientVersion;
      }

      if (osArch) {
        config.headers['odin-client-os-platform'] = osArch;
      }
      requestDebug && debug(`api: [${apiVersion}] OsArch: [${osArch}] clientVersion: [${clientVersion}]`);
      return config;
    });
  }
}

function extractUrlPath(u: string): string {
  requestDebug && debug(`URL: [${u}]`);
  if (u.startsWith('http://') || u.startsWith('https://')) {
    return extractFullPath(u);
  }

  // 如果不是以 http:// 开头但，即在web场景下，url 不包含 http://domain 部分
  // 此处伪造一个 只为取出其path
  const fakeUrl = `http://fake-domain.com${u}`;
  return extractFullPath(fakeUrl);
}

function extractFullPath(u: string): string {
  try {
    const url = new URL(u);
    return url.pathname;
  } catch (e) {
    console.warn(e);
    debug('请求路径解析失败');
    throw e;
  }
}

/*
header参数：
authorization：用户ID+空格+登录返回的authorization
这个可以

timestamp：当前请求时间戳
这个可以

token：访问token，md5(url（如：auth.game/get）+timestamp+nonce+约定密钥)
URL要规范定义，只包含 path 部分，不包含 问号及其后的部分，如以下URL，
/api/games?category=free&page=1&count=20 ，计算 token 时，只有 /api/games 参与运算

1、只有在需要用户登录才能访问的接口才加 authorization
2、所有接口的加上（authorization未登录就留空） timestamp，nonce，token， 按空格分隔 字段名为 odin-token

// path 为小写，输出token为小写，KEY与其他部分均维持大小写不处理
*/
function setOdinTokenInterceptor(client: AxiosInstance) {
  const tokenKey = OdinTokenKey;
  client.interceptors.request.use((config) => {
    const userStore = useUsersStore();
    const [userId, sessionId] = userStore.userInfoTuple;

    if (!Number.isNaN(userId)) {
      config.headers.authorization = `${userId} ${sessionId}`;
    }

    const nonce = generateRndStr(10);
    const timestamp = `${Math.floor((+new Date()) / 1000)}`;
    // config.headers.Timestamp = timestamp;

    const reqPath = extractUrlPath(config.url || '').toLowerCase();
    const rawToken = reqPath + timestamp + nonce + tokenKey;
    requestDebug && debug(`rawToken: [${rawToken}]`);
    const sign = md5(rawToken).toLowerCase();
    const token = `${timestamp} ${nonce} ${sign}`;
    config.headers['Odin-Token'] = token;
    requestDebug && debug(`odin-token: [${token}]`);
    return config;
  });
}

// 设置一些用户机器或网络相关的透传给接口
function setOriginInterceptor(client: AxiosInstance) {
  client.interceptors.request.use((config) => {
    config.headers['x-client-true-ip'] = getRealIp();
    return config;
  });
}

// 错误处理不弹请求失败信息的白名单
const errorHandleWhitelist = [
  'api/cdn_log',
  'api/auth.upload/upload2',
  'api/news',
  'api/auth.accelerator/open',
];

// 错误处理
function setAxiosErrorHandle(client: AxiosInstance) {
  client.interceptors.response.use((resp) => resp, (err) => {
    if (!(err instanceof AxiosError)) {
      // 未知问题
      return Promise.reject(err);
    }

    debug(`url: [${err.config?.url}]`);

    // 可以处理
    const resp = err.response;
    debug('网络异常报错信息===>', resp);
    const str = `api/${err.config?.url?.split('api/')[1]}`;
    let flag = true;
    errorHandleWhitelist.forEach((item) => {
      if (str.indexOf(item) !== -1) {
        flag = false;
      }
    });
    let handledError: OdinException;
    if (flag) {
      // 处理后只能返回包装错误
      handledError = (new OdinException('axios error')).withOrigin(err);
    } else {
      handledError = (new Exception('axios error')).withOrigin(err);
    }
    if (!resp) {
      // showWarn('网络异常，请检查网络');
      // Promise.reject(handledError);
      return Promise.reject(flag && warnMessage.showWarn('网络异常，请检查网络'));
    }
    if (resp.status === 401) {
      // 清除登录状态
      const usersStore = useUsersStore();
      usersStore.logout();
      // 再显示登录框
      showLoginModal();
      // 显示过登录框后，设定错误级别 使其只在开发者控制台中显示
      return Promise.reject(handledError.withLevel('WARN'));
    }

    // 处理 code 如果code为有效整数，且不为0，则此处处理
    const rspData = resp.data;
    if (typeof rspData === 'object' && rspData && objcontainField(rspData, 'code') && typeof rspData.code === 'number') {
      const handled = handleOdinErrCode(rspData.code, rspData);
      if (handled) {
        // 如果已处理 则降低级别到 warn
        return Promise.reject(handledError.withLevel('WARN'));
      }
    }

    return Promise.reject(err);
  });
}

function handleOdinErrCode(code: number, rsp: object): boolean {
  let rspMsg = '';
  if (rsp && objcontainField(rsp, 'info') && typeof rsp.info === 'string') {
    rspMsg = rsp.info;
  }
  if (code === ErrCode.vipExpired) {
    debug('会员过期错误？？？？？');
    // 跳转到订单页面
    if (rspMsg) {
      warnMessage.showWarn(rspMsg);
    }
    router.push('/charge/charge');
    return true;
  } if (code === ErrCode.realNameUncheck) {
    warnMessage.showWarn('需要先进行实名认证');
    // 弹出实名窗口
    showNameCheck();
    // TODO:
    return true;
  }
  return false;
}

export function initAxiosClientInterceptors(client: AxiosInstance) {
  setRequestTiming(client);
  setGlobalLoading(client);
  setYunDunInterceptor(client);
  setOdinServerInterceptor(client);
  setOdinTokenInterceptor(client);
  setAxiosErrorHandle(client);
  setOriginInterceptor(client);
}
